using System;
using System.Collections.Generic;
using System.IO;
using Storage.Core;

namespace Storage.Server
{
	internal class StorageOperationLogParser : IDisposable
	{
		public StorageOperationLogParser(string fileName)
		{
			fileHandle = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Read, FileShare.ReadWrite);
			deserializer = BinaryDeserializer.BeginRecord(fileHandle);
		}

		public StorageOperationLogParser()
			: this (StorageOperationLog.DefaultFileName) { }

		public void Parse(out Dictionary<Guid, PlainEntity> entities, out Dictionary<string, Scope> scopes)
		{
			Console.Out.WriteLine("StorageOperationLogParser. Started to parse OL from file.");
			entities = new Dictionary<Guid, PlainEntity>();
			scopes = new Dictionary<string, Scope>();

			while (fileHandle.Position < fileHandle.Length)
			{
				byte opCode = deserializer.ReadByte();
				switch(opCode)
				{
					case StorageOpCodes.AddOrUpdate:
						HandleAddOrUpdate(entities, scopes, PlainEntity.Deserialize(deserializer));
						break;
					case StorageOpCodes.Delete:
						HandleDelete(entities, scopes, deserializer.ReadGuid());
						break;
					default: throw new InvalidOperationException("Unknown opcode in operations log!");
				}
			}
			Console.Out.WriteLine("StorageOperationLogParser. Finished to parse OL.");
		}

		public void Dispose()
		{
			fileHandle.Close();
		}

		private static void HandleAddOrUpdate(Dictionary<Guid, PlainEntity> entities, Dictionary<string, Scope> scopes, PlainEntity entity)
		{
			if (!scopes.ContainsKey(entity.Type))
				scopes.Add(entity.Type, new Scope());

			// If an entity changes type, we must remove it from scope with old type.
			PlainEntity currentEntity;
			if (entities.TryGetValue(entity.Id, out currentEntity) && !currentEntity.Type.Equals(entity.Type))
			{
				Scope scope;
				if (scopes.TryGetValue(currentEntity.Type, out scope))
				{
					scope.Remove(currentEntity.Id);
					if (scope.Count <= 0)
						scopes.Remove(currentEntity.Type);
				}
			}

			entities[entity.Id] = entity;
			Scope actualScope;
			if (!scopes.TryGetValue(entity.Type, out actualScope))
			{
				actualScope = new Scope();
				actualScope.AddOrUpdate(entity);
				scopes.Add(entity.Type, actualScope);
			}
			else actualScope.AddOrUpdate(entity);
		}

		private static void HandleDelete(Dictionary<Guid, PlainEntity> entities, Dictionary<string, Scope> scopes, Guid id)
		{
			PlainEntity entity;
			if (entities.TryGetValue(id, out entity))
			{
				entities.Remove(entity.Id);
				var scope = scopes[entity.Type];
				scope.Remove(entity.Id);
				if (scope.Count <= 0)
					scopes.Remove(entity.Type);
			}
		}

		private readonly IBinaryDeserializer deserializer;
		private readonly FileStream fileHandle;
	}
}